package com.wondream.myframework.app.common.util;

import com.google.common.collect.Sets;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Snowflake的结构如下（每部分用-分开）：
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 00
 * <p>
 * 对应个数 1 - 41 - 4 - 4 - 12
 * <p>
 * 1位标识，由于long基本类型在Java中是带符号的，正数为0，负数是1，id一般是正数，所以最高位是0
 * <p>
 * 41位时间戳（毫秒级），注意，41位时间戳不是存储当前时间的时间戳，而是存储时间戳的差值（当前时间戳 - 开始时间戳），
 * 这里的开始时间戳，一般是我们的id生成器开始使用的时间，由我们的程序来指定的
 * <p>
 * 10位的数据机器位，可以部署在1024个节点，包括5位的datacenterId和workerId
 * <p>
 * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒（同一机器，同一时间戳）产生4096个ID序列号
 * <p>
 * 加起来刚好64位，为一个long型
 * Snowflake的优点是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞（由数据中心ID和机器ID作区分），并且效率较高。
 * 经测试，每秒能产生26万左右的ID
 */
public class SnowflakeUtils {

    public static final int[] EMPTY_INT_ARRAY = new int[0];

    // 开始时间戳
    public static final long twepoch = 1609430400000L;

    // 机器id所占的位数
    public static final long workerIdBits = 5L;

    // 数据中心id所占的位数
    public static final long datacenterIdBits = 5L;

    // 支持的最大机器id，结果是31
    public static final long maxWorkId = -1L ^ (-1L << workerIdBits);

    // 支持的最大数据中心id，结果是31
    public static final long maxDataCenterId = -1L ^ (-1L << datacenterIdBits);

    // 序列在id中占的位数
    public static final long sequenceBits = 12L;

    // 机器id向左移12位
    public static final long workerIdShift = sequenceBits;

    // 数据中心id向左移17位（5+12）
    public static final long dataCenterIdShift = sequenceBits + workerIdBits;

    // 时间戳向左移22位（5+5+12）
    public static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    // 生成序列的掩码
    public static final long sequenceMask = -1L ^ (-1L << sequenceBits);

    // 工作机器ID(0-31)
    public static long workerId;

    // 数据中心ID(0-31)
    public static long dataCenterId;

    // 毫秒内序列(0-4096)
    public static long sequence = 0L;

    // 上次生成ID的时间戳
    public static long lastTimestamp = -1L;

    static {
        workerId = getWorkId();
        dataCenterId = getDataCenterId();
    }

    /**
     * 获得下一个ID(该方法是县城安全的)
     */
    public static synchronized long nextId() {
        long timestamp = timeGen();
        // 如果当前时间小雨上一次ID生成的时间戳，说明系统时钟回退过，这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("系统时钟回退");
        }
        sequence = (sequence + 1) & sequenceMask;
        // 毫秒内序列溢出
        if (sequence == 0) {
            // 如果是同一时间生成的，则进行毫秒内序列
            if (lastTimestamp == timestamp) {
                // 阻塞到下一个毫秒，获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
            // 上次生成ID的时间戳
            lastTimestamp = timestamp;
        }
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    /**
     * 阻塞到下一个毫秒，获得新的时间戳
     */
    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timeGen();
        }
        return timestamp;
    }

    /**
     * 以毫秒为单位的当前时间戳
     */
    private static long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 获取机器id
     */
    private static long getWorkId() {
        try {
            String hostAddress = InetAddress.getLocalHost().getHostAddress();
            int[] ints = toCodePoints(hostAddress);
            int sums = 0;
            for (int b : ints) {
                sums += b;
            }
            return (long) (sums % 32);
        } catch (UnknownHostException e) {
            // 如果获取失败，则使用随机数备用
            return GmidSystemUtils.nextLong(0, 31);
        }
    }

    /**
     * 获取数据中心id
     */
    private static long getDataCenterId() {
        try {
            int[] ints = toCodePoints(GmidSystemUtils.getHostName());
            int sums = 0;
            for (int b : ints) {
                sums += b;
            }
            return (long) (sums % 32);
        } catch (Exception e) {
            // 如果获取失败，则使用随机数备用
            return GmidSystemUtils.nextLong(0, 31);
        }
    }

    private static int[] toCodePoints(final CharSequence str) {
        return GmidSystemUtils.toCodePoints(str);
    }

    protected long getTimestamp(long id) {
        return (id >> timestampLeftShift) + twepoch;
    }

    protected long getMinimumId(long timestamp) {
        return (timestamp - twepoch) << timestampLeftShift;
    }


    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Set<Object> set = Sets.newConcurrentHashSet();
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 200; j++) {
                        long id = SnowflakeUtils.nextId();
                        System.out.println(Thread.currentThread().getName() + ":" + id);
                        set.add(id);
                    }
                }
            });
        }
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(set.size());
        DateTime startTime = DateTime.parse("2021-01-01 00:00:00", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("生成ID的起始时间：" + startTime.toDate().getTime());
    }

}
